#/usr/bin/env python                     # -*- coding: utf-8 -*-
"""Execute various commands.
"""

import __builtin__
from IQ import converter, formats, subcommands, utils
import os
import re
import simplejson
import subprocess
import sys
import tempfile

spec = None

class SubprocessError(Exception): pass

def read_specification():
    global spec
    data = utils.commands_json()
    spec = simplejson.loads(data)


def usage_main():
    print >>sys.stderr, 'Usage: iq command args'
    print >>sys.stderr, 'Commands:', ' '.join(spec.keys())
    maxlen = max(len(cmd) for cmd in spec)
    for cmd in spec:
        print >>sys.stderr, ' %s: %s' % (cmd.rjust(maxlen), spec[cmd]['doc'])
    sys.exit(1)
    
def usage(cmd):
    cmdspec = spec[cmd]
    signature = cmdspec['signature']
    print >>sys.stderr, cmd, '--', cmdspec['doc']
    print >>sys.stderr, "Usage: iq", cmd, ' '.join(signature)
    maxlen = max(len(arg) for arg in signature)
    for arg in signature:
        where = (x for x in ('inputs', 'outputs', 'constraints')
                   if arg in cmdspec[x]).next()
        if where == 'inputs': doc = 'input file: '
        elif where == 'outputs': doc = 'output file: '
        elif where == 'constraints': doc = 'constraint: '
        else: assert False, "unknown kind of arg in signature of " + cmd
        doc += cmdspec[where][arg]['doc']
        doc += ' (' + cmdspec[where][arg]['data type'] + ')'
        print >>sys.stderr, " %s: %s" % (arg.rjust(maxlen), doc)
    sys.exit(1)

def execute(cmd, args):
    cmdspec = spec[cmd]
    signature = cmdspec['signature']
    if len(args) != len(signature):
        print >>sys.stderr, "Command %s expects %d arguments, got %d\n"  % \
              (cmd, len(signature), len(args))
        usage(cmd)

    argmapping = {}
    for name, arg in zip(signature, args):
        if name in cmdspec['inputs'] or name in cmdspec['outputs']:
            # arg is a filename or constraint
            argmapping[name] = arg
        elif name in cmdspec['constraints']:
            # arg specifies a constraint of some type, typically int or float
            type = cmdspec['constraints'][name]['data type']
            type = getattr(__builtin__, type)
            argmapping[name] = type(arg)
        else:
            raise KeyError, \
                "Command %s has unexplained arg %s in signature" \
                % (cmd, name)

    tmpdir = None
    if cmdspec['temporaries'] and len(cmdspec['temporaries']):
        tmpdir = tempfile.mkdtemp()
        for i,name in enumerate(cmdspec['temporaries']):
            argmapping[name] = os.path.join(tmpdir, 'fil'+`i`)

    for line in cmdspec['command sequence']:
        execute1(line, argmapping)

    if tmpdir is not None:
        for name in cmdspec['temporaries']:
            os.unlink(argmapping[name])
        os.rmdir(tmpdir)

def system(cmd, args):
        if cmd.startswith('java'):
            cmd = 'java'
            args = ['-cp'] + [utils.find_executable(args[0])] + args[1:]
        else:
            cmd = utils.find_executable(cmd)
        stdin,stdout,stderr = None,None,None
        args = [ str(a) for a in args ]
        for a in args[:]:
            if a.startswith('<'):
                stdin = open(a[1:], 'r')
                args.remove(a)
            elif a.startswith('>'):
                stdout = open(a[1:], 'w')
                args.remove(a)
            elif a.startswith('2>'):
                stderr = open(a[2:], 'w')
                args.remove(a)
        try:
            print '==>', ' '.join([cmd] + args)
            retval = subprocess.call([cmd]+args, stdin=stdin, stdout=stdout, stderr=stderr)
            if stdin is not None: stdin.close()
            if stdout is not None: stdout.close()
            if stderr is not None: stderr.close()
            if retval < 0:
                raise SubprocessError, \
                    'Command %s received signal %d' % (cmd, -retval)
            elif retval > 0:
                raise SubprocessError, \
                    'Command %s returned error code %d' % (cmd, retval)
        except OSError, e:
            raise SubprocessError, 'Cannot execute %s: %s' % (cmd, e)

_varsub = re.compile(r'\$(?:\[(?P<modifier>[^\]]+)\])?\{(?P<variable>[^}]+)\}')

def execute1(line, argmapping):
    def substitute(match):
        mod = match.group('modifier')
        if mod is None:
            return argmapping[match.group('variable')]
        elif mod == 'fmt':
            raise NotImplementedError
        else:
            assert false

    args = []
    for word in line:
        word = _varsub.sub(substitute, word)
        args.append(word)

    if args[0] == 'system':
        system(args[1], args[2:])
    else:
        fun = subcommands.__dict__[args[0]]
        fun(*args[1:])

def main():
    read_specification()
    if len(sys.argv) < 2:
        usage_main()
    elif sys.argv[1] == '.commands.json': # secret internal command...
        simplejson.dump(spec, sys.stdout)
    else:
        execute(sys.argv[1], sys.argv[2:])
